package mt.omid.dimomuth;


import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import static mt.omid.dimomuth.internal.ErrorList.KRN_BAD_CFG_FILE;
import static mt.omid.dimomuth.internal.ErrorList.KRN_NON_SFA_EXCP;
import static mt.omid.dimomuth.internal.ErrorList.KRN_SFA_EXCP;
import static mt.omid.dimomuth.internal.ErrorList.OK;

import java.util.HashMap;
import java.util.StringTokenizer;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;

import mt.omid.dimomuth.bus.DataBus;
import mt.omid.dimomuth.internal.ConfigManager;
import mt.omid.dimomuth.internal.ConfigurationException;
import mt.omid.dimomuth.internal.SFAException;
import mt.omid.dimomuth.mgm.ManagementIf;
import mt.omid.dimomuth.mgm.MgmHandler;
import mt.omid.dimomuth.mgm.MgmServerException;
import mt.omid.dimomuth.statistics.StatManager;


/**
 * Main class.
 */
public class Kernel
{

    // private ExecutorService taskList;
    private HashMap<String, Module>	 modList;
    private ConfigManager		   cm;
    private String			  confDelimiter   = ",";
    private ManagementIf		    mgmIf;
    private DataBus			 bus;
    private StatManager		     statManager;
    private static int		      instanceId      = 0;

    // Logging Attributes
    private static String		   logID	   = Kernel.class.getSimpleName();     ;
    private static Logger		   logger	  = Logger.getLogger( Kernel.logID );
    private static Level		    lvl	     = Level.FINEST;
    private KernelMgmHandler		km;
    private boolean			 isManager       = false;

    // Executor definition
    private static ScheduledExecutorService scheduler;
    private int			     numberOfModule  = 0;
    private int			     delayToRelaunch = 0;

    static
    {
	Kernel.setLoggingLevel( lvl );
	logger.fine( "Set logger level to " + lvl ); //FIXME Check it maybe lead to null pointer exception 
    }


    public Kernel( String cfgAddr ) throws ConfigurationException, MgmServerException
    {
	logger.finest( "Create Kernel instance and read kernel configuration." );
	cm = new ConfigManager( cfgAddr );
	Kernel.instanceId = cm.getIntProperty( "krn.instid", 0 );

	setLoggingLevel( Level.parse( cm.getProperty( "krn.log.lvl", "WARNING" ) ) );

	mgmIf = new ManagementIf( Integer.parseInt( cm.getProperty( "krn.mgm.port" ) ), logger );
	km = new KernelMgmHandler( this );
	mgmIf.start( km );

	this.isManager = cm.getProperty( "krn.mgr", "false" ).equalsIgnoreCase( "true" ) ? true
		: false;

	modList = new HashMap<String, Module>();
	delayToRelaunch = Integer.parseInt( cm.getProperty( "krn.mod.dtr", "0" ) );

	// Initiating shared data bus
	bus = DataBus.getInstance();
	bus.setPort( Integer.parseInt( cm.getProperty( "krn.dbus.port", "9899" ) ) );
	bus.setQueueSize( Integer.parseInt( cm.getProperty( "krn.dbus.qsize", "100" ) ) );

	statManager = new StatManager();
	statManager.start();
    }

    public boolean isKernelManager()
    {
	return this.isManager;
    }

    public static int getInstanceId()
    {
	return Kernel.instanceId;
    }

    public int startModules()
    {
	logger.finest( "Starting Modules..." );
	scheduler = Executors.newScheduledThreadPool( numberOfModule );

	for( Module mInst : modList.values() )
	{
	    logger.finer( "Starting an instance[" + mInst.getClass().getSimpleName() + "]" );
	    ScheduledFuture sf = scheduler.scheduleWithFixedDelay( mInst, 0, delayToRelaunch,
		    TimeUnit.SECONDS );
	    mInst.setExecutor( sf );
	}
	return OK;
    }

    public int loadModules()
    {
	logger.finest( "Loading Modules..." );
	// Commented automatically
	StringTokenizer modNames = new StringTokenizer( cm.getProperty( "krn.mod.list" ),
		confDelimiter );
	StringTokenizer modClss = new StringTokenizer( cm.getProperty( "krn.mod.clss" ),
		confDelimiter );
	/* -> Change 1 (Module.java)
	StringTokenizer cfgAddr = new StringTokenizer( cm.getProperty( "krn.mod.cfg" ),
		confDelimiter );
	*/

	while( modNames.hasMoreTokens() && modClss.hasMoreTokens() )
	{
	    Module modInstance;
	    String instanceName = "";
	    String className = "";
	    try
	    {
		// initialize module -- one by one as listed in config file
		instanceName = modNames.nextToken();
		className = modClss.nextToken();

		logger.finer( "Initiating a module instance [" + instanceName + "@" + className
			+ "] #" + numberOfModule );

		modInstance = ( Module ) Class.forName( className ).newInstance();
		modList.put( instanceName, modInstance );
		numberOfModule++;
		modInstance.initialize();
	    }
	    catch( IllegalArgumentException e )
	    {
		logger.log( WARNING, instanceName + " module initialization failed.", e );
	    }
	    catch( SecurityException e )
	    {
		logger.log( WARNING, instanceName + " module initialization failed.", e );
	    }
	    catch( InstantiationException e )
	    {
		logger.log( WARNING, instanceName + " module initialization failed.", e );
	    }
	    catch( IllegalAccessException e )
	    {
		logger.log( WARNING, instanceName + " module initialization failed.", e );
	    }
	    catch( ClassNotFoundException e )
	    {
		logger.log( WARNING, className + " module class initialization failed.", e );
	    }
	    /* -> Chnage 1 (module.java)
	    catch( SFAException e )
	    {
	    logger.log( WARNING, instanceName + " module initialization failed.", e );
	    }
	    */
	    catch( Throwable t )
	    {
		logger
			.log(
				SEVERE,
				"Fatal Erorr occured in module initializaton. It have to be handled somewhere else. ",
				t );
	    }
	}
	return OK;
    }

    public void init()
    {
	return;
    }

    public void shutdown()
    {
	scheduler.shutdown();

	for( Module mInst : modList.values() )
	{
	    mInst.shutdown();
	    mInst.forceShutdown();
	}

	try
	{
	    Thread.sleep( 10000 );
	}
	catch( InterruptedException e )
	{
	    logger.log( INFO, "Shutdown Sleep exceptioned.", e );
	}
	scheduler.shutdownNow();
	mgmIf.shutdown();
    }

    public static void setLoggingLevel( Level lvl )
    {
	Kernel.lvl = lvl;
	logger.setLevel( lvl );
	setAllHandlersLevel( lvl );
    }

    private static void setAllHandlersLevel( Level lvl )
    {
	Handler hndl[] = Logger.getLogger( "" ).getHandlers();

	for( int i = 0; i < hndl.length; i++ )
	{
	    hndl[ i ].setLevel( lvl );
	}
    }

    public static void main( String [] args )
    {
	logger.finest( "Startup the kernel." );
	try
	{
	    // Commented to add using default kernel config file krn.cfg in working directory
	    /*
	    if( args.length < 1 )
	    {
	    logger.severe( KRN_BAD_CFG_FILE + ":No/Bad configuration file." );
	    System.exit( KRN_BAD_CFG_FILE );
	    }
	    */

	    String cfgFile = "";
	    if( args.length < 1 )
	    {
		logger.warning( "Use default kernel config file: "
			+ ConfigManager.DEFAULT_KRN_CFG_FILE );
		cfgFile = ConfigManager.DEFAULT_KRN_CFG_FILE;
	    }
	    else
	    {
		cfgFile = args[ 0 ];
	    }

	    Kernel _main = new Kernel( cfgFile );
	    if( _main.loadModules() == OK )
	    {
		logger.fine( "Modules loaded successfully. Go ahead for startup." );
		_main.startModules();
	    }
	    else
	    {
		logger.severe( "Modules	 loading failed." );
	    }
	}
	catch( SFAException sfaex )
	{
	    logger.log( SEVERE, "Error-" + KRN_SFA_EXCP + ":SFAException at Kernel start.", sfaex );
	    System.exit( KRN_SFA_EXCP );
	}
	catch( Throwable e )
	{
	    logger.log( WARNING, "Error-" + KRN_NON_SFA_EXCP
		    + ":Common Exception at Kernek startup.", e );
	    System.exit( KRN_NON_SFA_EXCP );
	}
    }
}


class KernelMgmHandler implements MgmHandler
{

    private Kernel kernel;


    public KernelMgmHandler( Kernel krn )
    {
	this.kernel = krn;
    }

    @Override
    public String processRequest( String msg )
    {
	// FIXME - Implement Kernel management protocol
	return "NotImplementedYet";
    }

}
